﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NUnit.Framework;
using NUnit.Mocks;

using Working.Aplicação.Vendas;

using Working.Domínio;
using Working.Domínio.Vendas;
using Working.Domínio.Produtos;

namespace Working.Aplicação.Vendas.Teste
{
    [TestFixture]
    public class TesteControladorDeVendas
    {
        private CatalogoDeProdutos catálogo;
        private ControladorDeVendas controlador;
        private DynamicMock catálogoMock;

        [TestFixtureSetUp]
        public void InicializaSequênciaDeTestes()
        {
            //Cria simulador de acesso aos dados de produto
            catálogoMock = new DynamicMock(typeof(CatalogoDeProdutos));           
        }

        [SetUp]
        public void InicializaTeste()
        {
            catálogo = (CatalogoDeProdutos)catálogoMock.MockInstance;
            controlador = new ControladorDeVendas(catálogo);
        }

        [Test]
        public void TestaConstrutor()
        {            
            Assert.IsNull(controlador.VendaCorrente);
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void TestaContrutorComParâmetroNulo()
        {
            ControladorDeVendas controlador = new ControladorDeVendas(null);
        }

        [Test]
        public void TestaIniciarNovaVenda()
        {            
            controlador.IniciarNovaVenda();
            Assert.IsNotNull(controlador.VendaCorrente);
            Assert.AreEqual(EstadoDaVenda.InserindoItens, controlador.Estado);
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaIniciarNovaVendaComOutraVendaInserindoItens()
        {
            string itemID = "00001";
            catálogoMock.ExpectAndReturn("ObterProduto", new Produto(), itemID);

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem(itemID);
            catálogoMock.Verify();

            //Tenta iniciar uma nova venda
            controlador.IniciarNovaVenda();
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaIniciarNovaVendaComOutraVendaAguardandoPagamento()
        {
            ItemID itemID = new ItemID("00001");
            Produto produto = new Produto(itemID, "Camiseta", 25.90m);
            catálogoMock.ExpectAndReturn("ObterProduto", produto, itemID);

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem("00001");
            controlador.CompletarVenda();
            catálogoMock.Verify();

            //Tenta iniciar uma nova venda
            controlador.IniciarNovaVenda();
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaIniciarNovaVendaComOutraVendaAguardandoFinalização()
        {
            ItemID itemID = new ItemID("00001");
            Produto produto = new Produto(itemID, "Camiseta", 25.90m);
            catálogoMock.ExpectAndReturn("ObterProduto", produto, itemID);

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem("00001");
            controlador.CompletarVenda();
            controlador.FazerPagamentoEmDinheiro(controlador.VendaCorrente.Total);
            catálogoMock.Verify();

            //Tenta iniciar uma nova venda
            controlador.IniciarNovaVenda();
        }

        [Test]
        public void TestaIniciarNovaVendaApósFinalizaçãoDeOutra()
        {
            ItemID itemID = new ItemID("00001");
            decimal valorDoProduto = 500m;
            Produto produto = new Produto(itemID, "Camiseta", valorDoProduto);
            catálogoMock.ExpectAndReturn("ObterProduto", produto, itemID);

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem("00001");
            controlador.CompletarVenda();
            controlador.FazerPagamentoEmDinheiro(valorDoProduto);
            controlador.FinalizarVenda();

            Assert.AreEqual(EstadoDaVenda.EsperandoVenda, controlador.Estado);
            Assert.IsNull(controlador.VendaCorrente);

            //Tenta iniciar uma nova venda
            controlador.IniciarNovaVenda();
            Assert.AreEqual(EstadoDaVenda.InserindoItens, controlador.Estado);
            Assert.IsNotNull(controlador.VendaCorrente);            
        }

        [Test]
        public void TestaAdicionarItemVálido()
        {
            //Simula os produtos que serão retornados
            int quantidadeDeItens = 2;
            decimal totalVenda = 21.40m;
            string itemID1 = "00001";
            Produto produto1 = new Produto(itemID1, "Porta-Lápis", 5.50m);

            string itemID2 = "00002";
            Produto produto2 = new Produto(itemID2, "Caderno", 15.90m);

            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);
            catálogoMock.ExpectAndReturn("ObterProduto", produto2, itemID2);

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem(itemID1);
            controlador.AdicionarItem(itemID2);

            Assert.AreEqual(quantidadeDeItens, controlador.VendaCorrente.Itens.Count);
            Assert.AreEqual(totalVenda, controlador.VendaCorrente.Total);           

            catálogoMock.Verify();
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaAdicionarItemInexistênte()
        {
            //Simula os produtos que serão retornados
            string itemID1 = "00001";
            Produto produto1 = null;

            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem(itemID1);
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaAdicionarItemEsperandoPorVenda()
        {
            //Simula os produtos que serão retornados
            string itemID1 = "00001";
            Produto produto1 = new Produto(itemID1, "Porta-Lápis", 6m);

            //Venda não foi iniciada
            controlador.AdicionarItem(itemID1);
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaAdicionarItemAguardandoFinalização()
        {
            //Simula os produtos que serão retornados
            decimal totalVenda = 10m;
            string itemID1 = "00001";
            Produto produto1 = new Produto(itemID1, "Porta-Lápis", 10m);

            //Produto adicional que será adicionado após a realização do pagamento
            string itemID2 = "00002";
            Produto produto2 = new Produto(itemID2, "Produto adicional", 5m);

            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem(itemID1);
            controlador.CompletarVenda();
            controlador.FazerPagamentoEmDinheiro(totalVenda);
            catálogoMock.Verify();

            //Adiciona novo item após realizar pagamento
            controlador.AdicionarItem(itemID2);
        }

        [Test]
        public void TestaAdicionarItemComDiscriminaçãoDeQuantidade()
        {
            string itemID1 = "0123456789";
            int quantidadeDeItensProduto1 = 5;
            decimal valorDoProduto1 = 90m;
            decimal subTotalItem1 = quantidadeDeItensProduto1 * valorDoProduto1;
            Produto produto1 = new Produto(itemID1, "Pen Drive", valorDoProduto1);            
            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);
            
            string itemID2 = "987654321";
            int quantidadeDeItensProduto2 = 3;
            decimal valorDoProduto2 = 350m;
            decimal subTotalItem2 = quantidadeDeItensProduto2 * valorDoProduto2;
            Produto produto2 = new Produto(itemID2, "HD", valorDoProduto2);
            catálogoMock.ExpectAndReturn("ObterProduto", produto2, itemID2);

            decimal totalDaVendaEsperado = subTotalItem1 + subTotalItem2;

            controlador.IniciarNovaVenda();

            //Adiciona os produtos, discriminando a quantidade de itens
            controlador.AdicionarItem(itemID1, quantidadeDeItensProduto1);
            controlador.AdicionarItem(itemID2, quantidadeDeItensProduto2);

            //Verifica 1º item da venda
            ItemDeVenda item1 = controlador.VendaCorrente.Itens[0];
            Assert.AreEqual(quantidadeDeItensProduto1, item1.Quantidade);
            Assert.AreEqual(produto1, item1.Produto);
            Assert.AreEqual(subTotalItem1, item1.SubTotal);

            //Verifica 2º item da venda
            ItemDeVenda item2 = controlador.VendaCorrente.Itens[1];
            Assert.AreEqual(quantidadeDeItensProduto2, item2.Quantidade);
            Assert.AreEqual(produto2, item2.Produto);
            Assert.AreEqual(subTotalItem2, item2.SubTotal);

            //Verifica o total da venda calculado
            Assert.AreEqual(totalDaVendaEsperado, controlador.VendaCorrente.Total);

            catálogoMock.Verify();
        }


        [Test]
        public void TestaAdicionarItemSemDiscriminaçãoDeQuantidade()
        {
            string itemID1 = "0123456789";
            decimal valorDoProduto1 = 90m;
            Produto produto1 = new Produto(itemID1, "Pen Drive", valorDoProduto1);
            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);

            string itemID2 = "987654321";
            decimal valorDoProduto2 = 350m;
            Produto produto2 = new Produto(itemID2, "HD", valorDoProduto2);
            catálogoMock.ExpectAndReturn("ObterProduto", produto2, itemID2);

            decimal totalDaVendaEsperado = valorDoProduto1 + valorDoProduto2;

            controlador.IniciarNovaVenda();

            //Adiciona os produtos, discriminando a quantidade de itens
            controlador.AdicionarItem(itemID1);
            controlador.AdicionarItem(itemID2);

            //Verifica 1º item da venda
            ItemDeVenda item1 = controlador.VendaCorrente.Itens[0];
            Assert.AreEqual(1, item1.Quantidade);
            Assert.AreEqual(produto1, item1.Produto);
            Assert.AreEqual(valorDoProduto1, item1.SubTotal);

            //Verifica 2º item da venda
            ItemDeVenda item2 = controlador.VendaCorrente.Itens[1];
            Assert.AreEqual(1, item2.Quantidade);
            Assert.AreEqual(produto2, item2.Produto);
            Assert.AreEqual(valorDoProduto2, item2.SubTotal);

            //Verifica o total da venda calculado
            Assert.AreEqual(totalDaVendaEsperado, controlador.VendaCorrente.Total);

            catálogoMock.Verify();
        }

        [Test]
        public void TestaCompletarVendaSemItens()
        {
            controlador.IniciarNovaVenda();
            controlador.CompletarVenda();
            //Caso não exista itens na venda, saldo ficará zerado, não necessitando pagamento
            Assert.AreEqual(0m, controlador.VendaCorrente.Saldo());
            Assert.AreEqual(EstadoDaVenda.AguardandoFinalização, controlador.Estado);
            Assert.IsTrue(controlador.VendaCorrente.EstáCompleta);
        }

        [Test]
        public void TestaCompletarVendaComItens()
        {
            string itemID1 = "0123456789";
            decimal valorDoProduto1 = 90m;
            Produto produto1 = new Produto(itemID1, "Pen Drive", valorDoProduto1);
            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);

            string itemID2 = "987654321";
            decimal valorDoProduto2 = 350m;
            Produto produto2 = new Produto(itemID2, "HD", valorDoProduto2);
            catálogoMock.ExpectAndReturn("ObterProduto", produto2, itemID2);
            decimal totalDaVenda = valorDoProduto1 + valorDoProduto2;

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem(itemID1);
            controlador.AdicionarItem(itemID2);
            controlador.CompletarVenda();
            //Caso não exista itens na venda, saldo ficará zerado, não necessitando pagamento
            Assert.AreEqual(totalDaVenda, controlador.VendaCorrente.Total);
            Assert.AreEqual(totalDaVenda * -1, controlador.VendaCorrente.Saldo());
            Assert.AreEqual(EstadoDaVenda.AguardandoPagamento, controlador.Estado);
            Assert.IsTrue(controlador.VendaCorrente.EstáCompleta);
            catálogoMock.Verify();
        }

        [Test]
        public void TestaFazerPagamentoEmDinheiro()
        {
            string itemID1 = "0123456789";
            decimal valorDoProduto1 = 3500m;
            Produto produto1 = new Produto(itemID1, "Notebook", valorDoProduto1);
            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);

            controlador.IniciarNovaVenda();

            controlador.AdicionarItem(itemID1);

            controlador.CompletarVenda();
            controlador.FazerPagamentoEmDinheiro(valorDoProduto1);
            Assert.AreEqual(EstadoDaVenda.AguardandoFinalização, controlador.Estado);
            Assert.AreEqual(0, controlador.VendaCorrente.Saldo());
            catálogoMock.Verify();
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaFazerPagamentoEmDinheiroEsperandoVenda()
        {
            controlador.FazerPagamentoEmDinheiro(1000m);
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaFazerPagamentoEmDinheiroInserindoItens()
        {
            string itemID1 = "0123456789";
            decimal valorDoProduto1 = 3500m;
            Produto produto1 = new Produto(itemID1, "Notebook", valorDoProduto1);
            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);

            controlador.IniciarNovaVenda();
            controlador.AdicionarItem(itemID1);
            controlador.FazerPagamentoEmDinheiro(valorDoProduto1);
        }

        [Test]
        public void TestaFazerPagamentoEmDinheiroComTroco()
        {
            string itemID1 = "0123456789";
            decimal valorDoProduto1 = 3500m;
            Produto produto1 = new Produto(itemID1, "Notebook", valorDoProduto1);
            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);
            decimal valorPagamento = 4000m;
            decimal trocoExperado = 500m;

            controlador.IniciarNovaVenda();

            controlador.AdicionarItem(itemID1);

            controlador.CompletarVenda();
            controlador.FazerPagamentoEmDinheiro(valorPagamento);

            Assert.AreEqual(EstadoDaVenda.AguardandoFinalização, controlador.Estado);
            Assert.AreEqual(trocoExperado, controlador.VendaCorrente.Saldo());
            catálogoMock.Verify();
        }


        [Test]
        public void TestaFazerPagamentoEmDinheiroComValorInsuficiente()
        {
            string itemID1 = "0123456789";
            decimal valorDoProduto1 = 3500m;
            Produto produto1 = new Produto(itemID1, "Notebook", valorDoProduto1);
            catálogoMock.ExpectAndReturn("ObterProduto", produto1, itemID1);
            decimal valorPagamento = 2000;
            decimal saldoEsperado = -1500m;

            controlador.IniciarNovaVenda();

            controlador.AdicionarItem(itemID1);

            controlador.CompletarVenda();
            controlador.FazerPagamentoEmDinheiro(valorPagamento);

            //Continua aguardando pagamento para saldar a dívida
            Assert.AreEqual(EstadoDaVenda.AguardandoPagamento, controlador.Estado);
            Assert.AreEqual(saldoEsperado, controlador.VendaCorrente.Saldo());
            catálogoMock.Verify();
        }

        [Test]
        public void TestaFinalizarVenda()
        {
            string itemID = "0001";
            decimal valorDoProduto = 100m;
            Produto produto = new Produto(itemID, "Vodka", valorDoProduto);
            catálogoMock.ExpectAndReturn("ObterProduto", produto, itemID);

            controlador.IniciarNovaVenda();

            controlador.AdicionarItem(itemID);

            controlador.CompletarVenda();

            controlador.FazerPagamentoEmDinheiro(valorDoProduto);

            controlador.FinalizarVenda();

            Assert.AreEqual(EstadoDaVenda.EsperandoVenda, controlador.Estado);
            Assert.IsNull(controlador.VendaCorrente);
            catálogoMock.Verify();            
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaFinalizarVendaEsperandoVenda()
        {
            controlador.FinalizarVenda();
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaFinalizarVendaInserindoItens()
        {
            string itemID = "0001";
            decimal valorDoProduto = 100m;
            Produto produto = new Produto(itemID, "Vodka", valorDoProduto);
            catálogoMock.ExpectAndReturn("ObterProduto", produto, itemID);

            controlador.IniciarNovaVenda();

            controlador.AdicionarItem(itemID);

            controlador.FinalizarVenda();
        }

        [Test]
        [ExpectedException(typeof(ApplicationException))]
        public void TestaFinalizarVendaAguardandoPagamento()
        {
            string itemID = "0001";
            decimal valorDoProduto = 100m;
            Produto produto = new Produto(itemID, "Vodka", valorDoProduto);
            catálogoMock.ExpectAndReturn("ObterProduto", produto, itemID);

            controlador.IniciarNovaVenda();

            controlador.AdicionarItem(itemID);

            controlador.CompletarVenda();

            controlador.FinalizarVenda();
        }
    }
}
